package com.ttg.common.utils.sm;

import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECMultiplier;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.FixedPointCombMultiplier;

import java.io.ByteArrayInputStream;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.security.*;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

class SMUtil {

    public static final String SM2PubHardKeyHead = "3059301306072A8648CE3D020106082A811CCF5501822D034200";

    static {
        try {
            Security.addProvider(new BouncyCastleProvider());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
    private static ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());
    private static ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());

    public static KeyPair generateKeyPair() {
        try {
            KeyPairGenerator kpGen = KeyPairGenerator.getInstance("EC", "BC");
            kpGen.initialize(ecParameterSpec, new SecureRandom());
            KeyPair kp = kpGen.generateKeyPair();
            return kp;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static byte[] privateKey(byte[] privatekey) throws Exception {
        ASN1Sequence seq = ASN1Sequence.getInstance(privatekey);

        ASN1Encodable asn1Encodable = seq.getObjectAt(2);
        DEROctetString eEROctetString = (DEROctetString) asn1Encodable;

        DLSequence dLSequence = (DLSequence) ASN1Sequence.fromByteArray(eEROctetString.getOctets());
        asn1Encodable = dLSequence.getObjectAt(1);
        eEROctetString = (DEROctetString) asn1Encodable;
        return eEROctetString.getOctets();
    }

    public static byte[] publicKey(byte[] pubkey) throws Exception {
        SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(pubkey);

        DERBitString publicKeyData = subjectPublicKeyInfo.getPublicKeyData();
        byte[] publicKey = publicKeyData.getEncoded();
        byte[] encodedPublicKey = publicKey;
        byte[] ecP = new byte[64];
        System.arraycopy(encodedPublicKey, 4, ecP, 0, ecP.length);

        byte[] certPKX = new byte[32];
        byte[] certPKY = new byte[32];
        System.arraycopy(ecP, 0, certPKX, 0, 32);
        System.arraycopy(ecP, 32, certPKY, 0, 32);
        return ecP;
    }

    public static byte[] decodeDERSignature(byte[] signature) throws Exception {
        ASN1InputStream stream = new ASN1InputStream(new ByteArrayInputStream(signature));

        try {
            ASN1Sequence primitive = (ASN1Sequence) stream.readObject();
            Enumeration enumeration = primitive.getObjects();
            BigInteger R = ((ASN1Integer) enumeration.nextElement()).getValue();
            BigInteger S = ((ASN1Integer) enumeration.nextElement()).getValue();
            byte[] bytes = new byte[64];
            byte[] r = format(R.toByteArray());
            byte[] s = format(S.toByteArray());
            System.arraycopy(r, 0, bytes, 0, 32);
            System.arraycopy(s, 0, bytes, 32, 32);
            return bytes;
        } catch (Exception var10) {
            throw new Exception();
        }
    }

    public static byte[] format(byte[] value) {
        if (value.length == 32) {
            return value;
        } else {
            byte[] bytes = new byte[32];
            if (value.length > 32) {
                System.arraycopy(value, value.length - 32, bytes, 0, 32);
            } else {
                System.arraycopy(value, 0, bytes, 32 - value.length, value.length);
            }

            return bytes;
        }
    }

    /**
     * SM2公钥加密
     *
     * @param data   明文字节数组
     * @param pubKey 公钥
     * @return
     */
    public static byte[] sm2EncryptDataForPubKey(byte[] data, String pubKey) throws KeyException {
        if (pubKey.length() != 128) {
            throw new KeyException("pubKey length isn`t 128 ");
        }
        String x = pubKey.substring(0, 64);
        String y = pubKey.substring(64, 128);
        PublicKey key = getPublickeyFromXY(new BigInteger(x, 16), new BigInteger(y, 16));

        BCECPublicKey localECPublicKey = (BCECPublicKey) key;
        ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(localECPublicKey.getQ(), ecDomainParameters);
        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));
        try {
            return sm2Engine.processBlock(data, 0, data.length);
        } catch (InvalidCipherTextException e) {
            throw new RuntimeException(e);
        }
    }

    public static BCECPublicKey getPublickeyFromXY(BigInteger x, BigInteger y) {
        ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecParameterSpec);
        return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
    }

    public static byte[] sm2DecryptDataForPriKey(byte[] data, String priKey) {
        BigInteger d = new BigInteger(priKey, 16);
        BCECPrivateKey localECPrivateKey = getPrivatekeyFromD(d);
        ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(localECPrivateKey.getD(), ecDomainParameters);
        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(false, ecPrivateKeyParameters);
        try {
            return sm2Engine.processBlock(data, 0, data.length);
        } catch (InvalidCipherTextException e) {
            throw new RuntimeException(e);
        }
    }

    public static BCECPrivateKey getPrivatekeyFromD(BigInteger d) {
        ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecParameterSpec);
        return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
    }

    public static SM2KeyVO generateKeyPair2() {
        SM2Factory sm2 = SM2Factory.getInstance();
        AsymmetricCipherKeyPair key = null;
        while (true) {
            key = sm2.eccKeyPairGenerator.generateKeyPair();
            if (((ECPrivateKeyParameters) key.getPrivate()).getD().toByteArray().length == 32) {
                break;
            }
        }
        ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate();
        ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic();
        BigInteger privateKey = ecpriv.getD();
        ECPoint publicKey = ecpub.getQ();
        SM2KeyVO sm2KeyVO = new SM2KeyVO();
        sm2KeyVO.setPublicKey(publicKey);
        sm2KeyVO.setPrivateKey(privateKey);
        // System.out.println("公钥: " + CryptoUtil.bin2hex(publicKey.getEncoded(true)));
        // System.out.println("私钥: " + CryptoUtil.bin2hex(privateKey.toByteArray()));
        return sm2KeyVO;
    }


    /**
     * bc加解密使用旧标c1||c2||c3，此方法在加密后调用，将结果转化为c1||c3||c2
     *
     * @param c1c2c3
     * @return
     */
    protected static byte[] changeC1C2C3ToC1C3C2(byte[] c1c2c3) {
        final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1; //sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。
        final int c3Len = 32; //new SM3Digest().getDigestSize();
        byte[] result = new byte[c1c2c3.length];
        System.arraycopy(c1c2c3, 0, result, 0, c1Len); //c1
        System.arraycopy(c1c2c3, c1c2c3.length - c3Len, result, c1Len, c3Len); //c3
        System.arraycopy(c1c2c3, c1Len, result, c1Len + c3Len, c1c2c3.length - c1Len - c3Len); //c2
        return result;
    }


    /**
     * bc加解密使用旧标c1||c3||c2，此方法在解密前调用，将密文转化为c1||c2||c3再去解密
     *
     * @param c1c3c2
     * @return
     */
    protected static byte[] changeC1C3C2ToC1C2C3(byte[] c1c3c2) {
        final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1; //sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。
        final int c3Len = 32; //new SM3Digest().getDigestSize();
        byte[] result = new byte[c1c3c2.length];
        System.arraycopy(c1c3c2, 0, result, 0, c1Len); //c1: 0->65
        System.arraycopy(c1c3c2, c1Len + c3Len, result, c1Len, c1c3c2.length - c1Len - c3Len); //c2
        System.arraycopy(c1c3c2, c1Len, result, c1c3c2.length - c3Len, c3Len); //c3
        return result;
    }



    public static void main(String[] args) throws Exception {
        String pk = "-----BEGIN PRIVATE KEY-----\n" + "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDgbx3YbqIZ22P9\n" + "kyOyLpi4Ad7TBxwkI9rBZYVR7SHDxPpKYgnsuYg7s1jsUcz9pEEeBmQvkC3hGjAF\n" + "MEH0Oadl+n8aY+pmQO15iAVHRXxVm8uIh6mks0KZQN+Ujxn/mkkn5pvoydf9P7FH\n" + "habtk7Q3B6wv80MsaYM4DYORJhrF67AmgbqNnrODlBtNB/WWhYU3ZWG1Herb+Sq7\n" + "XrO82qC6NSr5PUhFwXpuGaT0tDaTYwl23P0ktq8XPYQmFnVggKUcO0OONmqKadkl\n" + "TKJdP6bKgsKqMb5cZzf43yuDTA==\n" + "-----END PRIVATE KEY-----";
        System.out.println(getSm2PublicKeyFromPrivateKey(rsaPrivateKeyToSm2PrivateKey(pk)));
    }

    /**
     * RSA 私钥转 SM2 私钥
     *
     * @param rsaPrivateKey rsa 私钥
     * @throws
     */
    public static String rsaPrivateKeyToSm2PrivateKey(String rsaPrivateKey) {
        // final char[] toBase64 = {
        //         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
        //         'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
        //         't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
        //         'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '+', '/'
        //
        // };
        // final char[] hexChar = {
        //         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c',
        //         'd', 'e', 'f'};
        rsaPrivateKey = rsaPrivateKey.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replaceAll("\\r", "").replaceAll("\\n", "").replaceAll("\\s+", "").substring(0, 64);
        Map<Character, Character> base64mod16Map = new HashMap<Character, Character>() {{
            put('0', '0');
            put('1', '1');
            put('2', '2');
            put('3', '3');
            put('4', '4');
            put('5', '5');
            put('6', '6');
            put('7', '7');
            put('8', '8');
            put('9', '9');
            put('a', 'a');
            put('b', 'b');
            put('c', 'c');
            put('d', 'd');
            put('e', 'e');
            put('f', 'f');
            put('g', '0');
            put('h', '1');
            put('i', '2');
            put('j', '3');
            put('k', '4');
            put('l', '5');
            put('m', '6');
            put('n', '7');
            put('o', '8');
            put('p', '9');
            put('q', 'a');
            put('r', 'b');
            put('s', 'c');
            put('t', 'd');
            put('u', 'e');
            put('v', 'f');
            put('w', '0');
            put('x', '1');
            put('y', '2');
            put('z', '3');
            put('A', '4');
            put('B', '5');
            put('C', '6');
            put('D', '7');
            put('E', '8');
            put('F', '9');
            put('G', 'a');
            put('H', 'b');
            put('I', 'c');
            put('J', 'd');
            put('K', 'e');
            put('L', 'f');
            put('M', '0');
            put('N', '1');
            put('O', '2');
            put('P', '3');
            put('Q', '4');
            put('R', '5');
            put('S', '6');
            put('T', '7');
            put('U', '8');
            put('V', '9');
            put('W', 'a');
            put('X', 'b');
            put('Y', 'c');
            put('Z', 'd');
            put('+', 'e');
            put('/', 'f');

        }};

        StringBuilder sm2PrivateKey = new StringBuilder();
        for (char c : rsaPrivateKey.toCharArray()) {
            sm2PrivateKey.append(base64mod16Map.get(c));
        }
        return sm2PrivateKey.toString();

    }

    /**
     * 通过sm2 计算 sm2公钥
     *
     * @param sm2PrivateKey sm2 私钥
     * @return
     */
    public static String getSm2PublicKeyFromPrivateKey(String sm2PrivateKey) {
        SM2Factory sm2 = SM2Factory.getInstance();
        ECKeyPairGenerator eccKeyPairGenerator = sm2.eccKeyPairGenerator;
        Class clazz = eccKeyPairGenerator.getClass();
        try {
            Field paramsfield = clazz.getDeclaredField("params");
            paramsfield.setAccessible(true);
            ECDomainParameters params = (ECDomainParameters) paramsfield.get(eccKeyPairGenerator);
            ECPublicKeyParameters aPublic = (ECPublicKeyParameters) SMUtil.generateKeyPair(new BigInteger(sm2PrivateKey, 16), params).getPublic();
            return CryptoUtil.bin2hex(aPublic.getQ().getEncoded(false));
        } catch (NoSuchFieldException | IllegalAccessException e) {
            return "";
        }
    }

    public static AsymmetricCipherKeyPair generateKeyPair(BigInteger d, ECDomainParameters params) {
        BigInteger n = params.getN();
        int nBitLength = n.bitLength();
        int minWeight = nBitLength >>> 2;


        ECPoint Q = createBasePointMultiplier().multiply(params.getG(), d);

        return new AsymmetricCipherKeyPair(new ECPublicKeyParameters(Q, params), new ECPrivateKeyParameters(d, params));
    }

    protected static ECMultiplier createBasePointMultiplier() {
        return new FixedPointCombMultiplier();
    }

}
